/**  
 * @Title VelocityToolboxExtView.java
 * @date 2016年3月17日 下午5:02:55
 * @Copyright (c) 2016, unibroad.com Inc. All rights reserved.
 */
package com.strugglerz.web.base;

import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.velocity.Template;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.tools.Scope;
import org.apache.velocity.tools.ToolboxFactory;
import org.apache.velocity.tools.config.FactoryConfiguration;
import org.apache.velocity.tools.config.ToolConfiguration;
import org.apache.velocity.tools.config.ToolboxConfiguration;
import org.apache.velocity.tools.view.ServletUtils;
import org.apache.velocity.tools.view.ViewToolContext;
import org.springframework.core.NestedIOException;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.view.velocity.VelocityView;

import com.strugglerz.web.shiro.ShiroTool;

/**
 * @Description 重写 VelocityToolboxView ,使支持velocity-tool 2.0
 * @Class com.unibroad.touchus.web.base.VelocityToolboxExtView
 * @author suan@unibroad.com
 * @version V1.0
 */
public class VelocityExtView extends VelocityView {

	private static final String FRAGMENT_PAGE_TAG = "/fragments/";

	private static ToolboxFactory velocityToolboxFactory;

	private Lock lock = new ReentrantLock();

	private String toolboxConfigLocation;

	/**
	 * The default {@link #setLayoutUrl(String) layout url}.
	 */
	public static final String DEFAULT_LAYOUT_URL = "layout.vm";

	/**
	 * The default {@link #setLayoutKey(String) layout key}.
	 */
	public static final String DEFAULT_LAYOUT_KEY = "layout";

	/**
	 * The default {@link #setScreenContentKey(String) screen content key}.
	 */
	public static final String DEFAULT_SCREEN_CONTENT_KEY = "screen_content";

	private String layoutUrl = DEFAULT_LAYOUT_URL;

	private String layoutKey = DEFAULT_LAYOUT_KEY;

	private String screenContentKey = DEFAULT_SCREEN_CONTENT_KEY;

	/**
	 * Overridden to create a ChainedContext, which is part of the view package
	 * of Velocity Tools, as special context. ChainedContext is needed for
	 * initialization of ViewTool instances.
	 * 
	 * @see #initTool
	 */
	@Override
	protected Context createVelocityContext(Map<String, Object> model, HttpServletRequest request,
			HttpServletResponse response) throws Exception {

		ViewToolContext ctx = new ViewToolContext(getVelocityEngine(), request, response, getServletContext());

		// Create a ChainedContext instance.
		// ChainedContext ctx = new ChainedContext(new VelocityContext(model),
		// getVelocityEngine(),
		// request, response, getServletContext());

		ctx.putAll(model);

		lock.lock();
		try {
			if (velocityToolboxFactory == null && getToolboxConfigLocation() != null) {

				FactoryConfiguration toolBoxCfgFactory = ServletUtils.getConfiguration(
						getServletContext().getRealPath(getToolboxConfigLocation()), ctx.getServletContext(), false);

				// 当 shiro 没有启用的时候，不加载 shiroTool
				if (!ShiroTool.isShiroStarted()) {
					Collection<ToolboxConfiguration> tboxcfgs = toolBoxCfgFactory.getToolboxes();
					Iterator<ToolConfiguration> toolsCfgItr = null;
					if (!CollectionUtils.isEmpty(tboxcfgs)) {
						for (ToolboxConfiguration tboxcfg : tboxcfgs) {
							if (StringUtils.equalsIgnoreCase(tboxcfg.getScope(), Scope.APPLICATION)) {
								toolsCfgItr = tboxcfg.getTools().iterator();
								while (toolsCfgItr.hasNext()) {
									if (toolsCfgItr.next().getToolClass().equals(ShiroTool.class)) {
										toolsCfgItr.remove();
										break;
									}
								}
							}
						}
					}
				}
				velocityToolboxFactory = new ToolboxFactory();
				velocityToolboxFactory.configure(toolBoxCfgFactory);
			}
		} finally {
			lock.unlock();
		}

		if (velocityToolboxFactory.hasTools(Scope.REQUEST)) {
			ctx.addToolbox(velocityToolboxFactory.createToolbox(Scope.REQUEST));
		}

		if (velocityToolboxFactory.hasTools(Scope.SESSION)) {
			ctx.addToolbox(velocityToolboxFactory.createToolbox(Scope.SESSION));
		}

		if (velocityToolboxFactory.hasTools(Scope.APPLICATION)) {
			ctx.addToolbox(velocityToolboxFactory.createToolbox(Scope.APPLICATION));
		}

		return ctx;
	}

	/**
	 * Overridden to check for the ViewContext interface which is part of the
	 * view package of Velocity Tools. This requires a special Velocity context,
	 * like ChainedContext as set up by {@link #createVelocityContext} in this
	 * class.
	 */
	@Override
	protected void initTool(Object tool, Context velocityContext) throws Exception {
		// Velocity Tools 1.3: a class-level "init(Object)" method.
		Method initMethod = ClassUtils.getMethodIfAvailable(tool.getClass(), "init", Object.class);
		if (initMethod != null) {
			ReflectionUtils.invokeMethod(initMethod, tool, velocityContext);
		}
	}

	/**
	 * Overrides {@code VelocityView.checkTemplate()} to additionally check that
	 * both the layout template and the screen content template can be loaded.
	 * Note that during rendering of the screen content, the layout template can
	 * be changed which may invalidate any early checking done here.
	 */
	@Override
	public boolean checkResource(Locale locale) throws Exception {
		if (!super.checkResource(locale)) {
			return false;
		}

		try {
			// Check that we can get the template, even if we might subsequently
			// get it again.
			if (!StringUtils.contains(this.getUrl(), FRAGMENT_PAGE_TAG)) {
				getTemplate(this.layoutUrl);
			}
			return true;
		} catch (ResourceNotFoundException ex) {
			throw new NestedIOException("Cannot find Velocity template for URL [" + this.layoutUrl
					+ "]: Did you specify the correct resource loader path?", ex);
		} catch (Exception ex) {
			throw new NestedIOException("Could not load Velocity template for URL [" + this.layoutUrl + "]", ex);
		}
	}

	/**
	 * Overrides the normal rendering process in order to pre-process the
	 * Context, merging it with the screen template into a single value
	 * (identified by the value of screenContentKey). The layout template is
	 * then merged with the modified Context in the super class.
	 */
	@Override
	protected void doRender(Context context, HttpServletResponse response) throws Exception {

		if (StringUtils.contains(this.getUrl(), FRAGMENT_PAGE_TAG)) {
			super.doRender(context, response);
		} else {
			renderScreenContent(context);
			// Velocity context now includes any mappings that were defined
			// (via #set) in screen content template.
			// The screen template can overrule the layout by doing
			// #set( $layout = "MyLayout.vm" )
			String layoutUrlToUse = (String) context.get(this.layoutKey);
			if (layoutUrlToUse != null) {
				if (logger.isDebugEnabled()) {
					logger.debug("Screen content template has requested layout [" + layoutUrlToUse + "]");
				}
			} else {
				// No explicit layout URL given -> use default layout of this
				// view.
				layoutUrlToUse = this.layoutUrl;
			}
			mergeTemplate(getTemplate(layoutUrlToUse), context, response);
		}
	}

	/**
	 * The resulting context contains any mappings from render, plus screen
	 * content.
	 */
	private void renderScreenContent(Context velocityContext) throws Exception {
		if (logger.isDebugEnabled()) {
			logger.debug("Rendering screen content template [" + getUrl() + "]");
		}

		StringWriter sw = new StringWriter();
		Template screenContentTemplate = getTemplate(getUrl());
		screenContentTemplate.merge(velocityContext, sw);

		// Put rendered content into Velocity context.
		velocityContext.put(this.screenContentKey, sw.toString());
	}

	/**
	 * @param layoutUrl
	 *            to set - {参数含义描述}.
	 */
	public void setLayoutUrl(String layoutUrl) {
		this.layoutUrl = layoutUrl;
	}

	/**
	 * @param layoutKey
	 *            to set - {参数含义描述}.
	 */
	public void setLayoutKey(String layoutKey) {
		this.layoutKey = layoutKey;
	}

	/**
	 * @param screenContentKey
	 *            to set - {参数含义描述}.
	 */
	public void setScreenContentKey(String screenContentKey) {
		this.screenContentKey = screenContentKey;
	}

	/**
	 * Set a Velocity Toolbox config location, for example
	 * "/WEB-INF/toolbox.xml", to automatically load a Velocity Tools toolbox
	 * definition file and expose all defined tools in the specified scopes. If
	 * no config location is specified, no toolbox will be loaded and exposed.
	 * <p>
	 * The specified location string needs to refer to a ServletContext
	 * resource, as expected by ServletToolboxManager which is part of the view
	 * package of Velocity Tools.
	 * 
	 * @see org.apache.velocity.tools.view.servlet.ServletToolboxManager#getInstance
	 */
	public void setToolboxConfigLocation(String toolboxConfigLocation) {
		this.toolboxConfigLocation = toolboxConfigLocation;
	}

	/**
	 * Return the Velocity Toolbox config location, if any.
	 */
	protected String getToolboxConfigLocation() {
		return this.toolboxConfigLocation;
	}

}
